Write a custom CUDA kernel to optimize the ScaleNorm operator based on the provided PyTorch implementation using double precision (float64).

The mathematical definition derived from the source code is:
norm = sqrt(sum(x^2))
output = (input * weight) / (norm + eps) + bias

Key differences from standard LayerNorm/RMSNorm:
1. Normalization is based on L2 Norm (Euclidean norm), not Mean-Variance or RMS.
2. Epsilon is added *after* the square root.
3. Weight and Bias are vectors of size (C).

Problem Analysis:
1. Memory Bandwidth: The operation involves calculating the L2 norm (reduction), and then applying element-wise scaling and shifting. A naive implementation reads `x` twice (once for norm, once for scaling) and reads `weight`/`bias` once.
2. Precision: L2 norm accumulation of squared values requires high precision to avoid overflow/underflow or precision loss, justifying the use of `double`.

Optimization Strategy: Fused Register-Resident Kernel (Double Precision)

1. Block-per-Row Parallelism: Each thread block handles one sample row (dimension D).

2. Vectorized Access: Use `double2` types (128-bit) to load input, weight, and bias. This maximizes memory throughput.

3. Register Caching:
    - Load a slice of the input row into thread-local registers.
    - Pass 1 (In-Register): Compute the local sum of squares `x^2`.
    - Block Reduction: Sum the local squares to get the global sum of squares for the row.
    - Compute Scaling Factor: `inv_norm = 1.0 / (sqrt(sum_sq) + eps)`.
    - Pass 2 (In-Register): Compute the final result using the cached `x`. Load `weight` and `bias` (if present) in a streaming fashion during this pass to compute `x * weight * inv_norm + bias`.

4. Fused Arithmetic: Perform the entire logic in a single kernel launch.
  
Here's an example to show you the syntax of inline embedding custom CUDA operators in torch: The example given architecture is:   
  
```python
import torch
import torch.nn as nn
import torch.nn.init as init
import numbers
from typing import Union, List, Tuple
from torch import Size, Tensor

BATCH_SIZE = 1024
DIM = 2048
SHAPE = (BATCH_SIZE, DIM)

DTYPE = torch.float64

NORMALIZED_SHAPE = DIM
EPS_VAL = 1e-5
USE_BIAS = True 

class ScaleNorm(nn.Module):
    '''
    The PyTorch implementation of ScaleNorm from the paper Transformers without Tears: Improving the Normalization of Self-Attention.
    https://arxiv.org/abs/1910.05895
    '''
    def __init__(self, normalized_shape: Union[int, List[int], Size], eps: float, bias: bool) -> None:
        super(ScaleNorm, self).__init__()
        if isinstance(normalized_shape, numbers.Integral):
            normalized_shape = (normalized_shape,)
        self.normalized_shape = tuple(normalized_shape)
        self.eps = eps
        self.weight = nn.Parameter(torch.empty(self.normalized_shape, dtype=DTYPE))
        if bias:
            self.bias = nn.Parameter(torch.empty(self.normalized_shape, dtype=DTYPE))
        else:
            self.register_parameter('bias', None)

        self.reset_parameters()

    def reset_parameters(self) -> None:
        init.ones_(self.weight)
        if self.bias is not None:
            init.zeros_(self.bias)

    def forward(self, input: Tensor) -> Tensor:
        norm = torch.norm(input, p=2, dim=-1, keepdim=True)
        scalenorm = self.weight * input / (norm + self.eps)

        if self.bias is not None:
            scalenorm = scalenorm + self.bias

        return scalenorm

class Model(nn.Module):
    def __init__(self, normalized_shape, eps, bias):
        super(Model, self).__init__()
        self.norm = ScaleNorm(normalized_shape=normalized_shape, eps=eps, bias=bias)
    
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return self.norm(x)

def get_inputs():
    x = torch.randn(SHAPE, dtype=DTYPE)
    return [x.contiguous()]

def get_init_inputs():
    # Explicitly return all hyperparameters
    return [NORMALIZED_SHAPE, EPS_VAL, USE_BIAS]